package dex.compiler.interpreter;


import java.util.List;

import dex.compiler.checker.type.MemberType;
import dex.compiler.checker.type.Type;
import dex.compiler.checker.type.TypeManager;
import dex.compiler.interpreter.value.ArrayValue;
import dex.compiler.interpreter.value.IntegerValue;
import dex.compiler.interpreter.value.MemberValue;
import dex.compiler.interpreter.value.UndefinedValue;
import dex.compiler.interpreter.value.Value;
import dex.compiler.model.definition.Callable;
import dex.compiler.model.definition.Constructor;
import dex.compiler.model.definition.TypedName;
import dex.compiler.model.expression.ArrayExpression;
import dex.compiler.model.expression.Expression;
import dex.compiler.model.expression.IdentifierExpression;
import dex.compiler.model.expression.Infix;
import dex.compiler.model.expression.MemberExpression;
import dex.compiler.model.program.NameSearcher;
import dex.compiler.model.program.Path;
import dex.compiler.model.program.Programs;
import dex.compiler.model.statement.Assignment;
import dex.compiler.model.statement.Block;
import dex.compiler.model.statement.CallStatement;
import dex.compiler.model.statement.CatchBlock;
import dex.compiler.model.statement.Comment;
import dex.compiler.model.statement.Declaration;
import dex.compiler.model.statement.DecrementStatement;
import dex.compiler.model.statement.ForStatement;
import dex.compiler.model.statement.IfStatement;
import dex.compiler.model.statement.IncrementStatement;
import dex.compiler.model.statement.ReturnStatement;
import dex.compiler.model.statement.StatementWalker;
import dex.compiler.model.statement.SwitchCase;
import dex.compiler.model.statement.SwitchStatement;
import dex.compiler.model.statement.ThrowStatement;
import dex.compiler.model.statement.TryStatement;
import dex.compiler.model.statement.WhileStatement;


abstract class StatementExecutor extends StatementWalker {

	
	final protected State state;
	final protected TypeManager manager;
	
	
	public StatementExecutor(State state) {
		this.state = state;
		this.manager = state.getTypeManager();
	}


	/**
	 * Executes a function.  Or a constructor.
	 * 
	 * @param path         the path to the function to execute
	 * @param parameters   the parameters for that function
	 * @param cls          the class if the function is virtual, or null
	 *                     if the function is non-virtual
	 * @return  the returned value
	 */
	public Value execute(Path path, List<Value> parameters, MemberType cls) {
		state.enter(path, parameters);
		MemberValue that = null;
		if (cls != null) {
			that = new MemberValue(manager, cls);
			state.setLocal("this", that);
		}
		Callable f = (Callable)path.getDefinition();
		walk(f.getBody());
		Value result;
		if (f instanceof Constructor) {
			result = that;
		} else if (f.isVoid()) {
			result = null;
		} else {
			result = state.pop();
		}
		state.exit();
		return result;
	}


	@Override
	protected void walkTryStatement(TryStatement t) {
		state.mark();
		try {
			walk(t.getTrySection());
			state.reset();
		} catch (Ball ball) {
			state.reset();
			if (!checkCatch(ball, t.getCatchSection())) {
				throw ball;
			}
		} finally {
			if (t.getFinallySection() != null) {
				walk(t.getFinallySection());
			}
		}
	}


	private boolean checkCatch(Ball ball, List<CatchBlock> catchSection) {
		Type thrownType = ball.getBall().type();
		for (CatchBlock cb: catchSection) {
			Type catchType = manager.toType(state.context(), cb.getType());
			if (manager.isSubclass(thrownType, catchType)) {
				state.setLocal(cb.getIdentifier(), ball.getBall());
				walk(cb.getBlock());
				return true;
			}
		}
		return false;
	}

	
	@Override
	protected void walkAssignment(Assignment a) {
		assign(a.getOperator(), a.getLValue(), a.getRValue());
	}

	
	@Override
	protected void walkIncrementStatement(IncrementStatement s) {
	}
	
	
	@Override
	protected void walkDecrementStatement(DecrementStatement s) {
		
	}
	
	private void assign(Infix infix, Expression lvalue, Expression rvalue) {
		if (lvalue instanceof IdentifierExpression) {
			IdentifierExpression ie = (IdentifierExpression)lvalue;
			assign(infix, ie, rvalue);
		} else if (lvalue instanceof MemberExpression) {
			MemberExpression me = (MemberExpression)lvalue;
			assign(infix, me, rvalue);
		} else if (lvalue instanceof ArrayExpression) {
			ArrayExpression ae = (ArrayExpression)lvalue;
			assign(infix, ae, rvalue);
		} else {
			throw new IllegalArgumentException("Illegal lvalue: " + lvalue.getClass().getName());
		}		
	}


	private void assign(Infix infix, IdentifierExpression lvalue, Expression rvalue) {
		String id = lvalue.getIdentifier();
		if (state.localExists(id)) {
			assignLocal(infix, id, rvalue);
		} else {
			assignGlobal(infix, id, rvalue);
		}
	}

	
	private void assignGlobal(Infix infix, String id, Expression rvalue) {
		NameSearcher searcher = new NameSearcher(TypedName.class, id);
		List<Path> found = Programs.search(state.getProgram(), state.context(), searcher);
		String global = found.get(0).qualified();
		walk(rvalue);
		Value v = state.pop();
		if (infix != null) {
			Value l = state.getLocal(id);
			v = InfixEvaluator.operate(infix, l, v);
		}
		state.setGlobal(global, v);
	}
	
	private void assignLocal(Infix infix, String id, Expression rvalue) {
		walk(rvalue);
		Value v = state.pop();
		if (infix != null) {
			Value l = state.getLocal(id);
			v = InfixEvaluator.operate(infix, l, v);
		}
		state.setLocal(id, v);
		
	}


	private void assign(Infix infix, MemberExpression lvalue, Expression rvalue) {
		String memberName = lvalue.getMemberName();
		walk(lvalue.getOwner());
		MemberValue mv = (MemberValue)state.popReference();
		walk(rvalue);
		Value v = state.pop();
		if (infix != null) {
			Value left = mv.fields().get(memberName);
			v = InfixEvaluator.operate(infix, left, v);
		}
		mv.fields().put(memberName, v);
	}
	
	
	private void assign(Infix infix, ArrayExpression lvalue, Expression rvalue) {
		walk(lvalue.getArray());
		ArrayValue av = (ArrayValue)state.popReference();
		walk(lvalue.getIndex());
		IntegerValue iv = (IntegerValue)state.pop();
		
		Value[] array = av.value();
		int index = (int)iv.value();
		state.checkBounds(array, index);
		
		walk(rvalue);
		Value v = state.pop();
		
		if (infix != null) {
			Value left = array[index];
			v = InfixEvaluator.operate(infix, left, v);
		}
		array[index] = v;
	}


	@Override
	protected void walkIfStatement(IfStatement s) {
		walk(s.getTest());
		if (state.popBoolean()) {
			walk(s.getPositiveBranch());
		} else {
			if (s.getNegativeBranch() != null) {
				walk(s.getNegativeBranch());
			}
		}
	}


	@Override
	protected void walkWhileStatement(WhileStatement s) {
		walk(s.getTest());
		while (state.popBoolean()) {
			walk(s.getBody());
			walk(s.getTest());
		}
	}

	
	@Override
	protected void walkForStatement(ForStatement s) {
		state.addLocalScope();
		if (s.getInitializer() != null) {
			walk(s.getInitializer());
		}
		walk(s.getTest());
		while (state.popBoolean()) {
			walk(s.getBody());
			if (s.getModifier() != null) {
				walk(s.getModifier());
			}
			walk(s.getTest());
		}
		state.removeLocalScope();
	}
	
	
	public void enterBlock(Block s) {
		state.addLocalScope();
	}


	public void exitBlock(Block s) {
		state.removeLocalScope();
	}

	
	public void enterForStatement(ForStatement s) {
		// nop; handled by InterpreterWalker
	}


	public void exitForStatement(ForStatement s) {
		// nop; handled by InterpreterWalker
	}


	public void visitAssignment(Assignment s) {
		// nop; handled by InterpreterWalker
	}


	public void visitCallStatement(CallStatement s) {
		// nop; subexpression was visited, and it performed the call
	}


	public void visitCatchBlock(CatchBlock s) {
		// nop; handled by InterpreterWalker
	}


	public void visitComment(Comment s) {
		// nop, by definition
	}


	public void visitDeclaration(Declaration s) {
		Value v = s.getInitializer() == null ? UndefinedValue.INSTANCE : state.pop();
		state.setLocal(s.getIdentifier(), v);
	}


	public void visitDecrementStatement(DecrementStatement s) {
		// TODO Auto-generated method stub
	}


	public void visitIfStatement(IfStatement s) {
		// nop; handled by InterpreterWalker
	}


	public void visitIncrementStatement(IncrementStatement s) {
		// TODO Auto-generated method stub	
	}


	public void visitReturnStatement(ReturnStatement s) {
		// nop
	}


	public void visitSwitchCase(SwitchCase s) {
		// TODO Auto-generated method stub		
	}


	public void visitSwitchStatement(SwitchStatement s) {
		// TODO Auto-generated method stub
	}


	public void visitThrowStatement(ThrowStatement s) {
		// TODO Auto-generated method stub		
	}


	public void visitTryStatement(TryStatement s) {
		// TODO Auto-generated method stub		
	}


	public void visitWhileStatement(WhileStatement s) {
		// nop; handled by InterpreterWalker
	}

}
